#include <stdio.h>
#include <netinet/in.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <unistd.h>
#include <getopt.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <libgen.h>

#define ARRAY_NUM(x) (sizeof(x)/sizeof(x[0]))

void print_prog(char *progname);


int main(int argc,char **argv)
{

	int                    i=0;
	int                  rv=-1;
	int               lisfd=-1;
	int               clifd=-1;
	int             lis_port=0;
	char             buf[1024];

	int                maxfd=-1;
	int            fds_array[1024];
	fd_set              rdset;

	struct   sockaddr_in servaddr;
	int                  level;
	int                  opt=1;
	socklen_t           optlen;

	
	struct  option      longopts[]={
		{"port", required_argument, NULL, 'p'},
                {"help", no_argument, NULL, 'h'},
		{0,0,0,0},
	};

	while( (rv=getopt_long(argc,argv,"p:h",longopts,NULL)) != -1)
	{
		switch(rv)
		{
			case 'p':
				lis_port=atoi(optarg);
				break;
			case 'h':
				print_prog(argv[0]);
				break;
		}
	}

	if(!lis_port)
        {
                print_prog(basename(argv[0]));
                return -1;
        }

	lisfd=socket(AF_INET,SOCK_STREAM,0);
	if(lisfd<0)
	{
		printf("socket failure:%s",strerror(errno));
		return -1;
	}
	printf("socket lisfd[%d]  successfully\n",lisfd);

	setsockopt(lisfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
	memset(&servaddr,0,sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_port   = htons(lis_port);
	servaddr.sin_addr.s_addr =htonl(INADDR_ANY);

	rv=bind(lisfd,(struct sockaddr *)&servaddr,sizeof(servaddr));
	if(rv<0)
	{
		printf("bind lisfd failure:%s\n",strerror(errno));
		return -1;
	}
	printf("bind port[%d] successfully\n",lis_port);

	rv=listen(lisfd,13);
	if(rv<0) printf("listen failure:%s\n",strerror(errno));
	printf("listen [%d] successfully\n",lisfd);

	for(i=0;i<ARRAY_NUM(fds_array);i++)
	{
		fds_array[i]=-1;
	}
	printf("fds_array set -1 ok\n");
	fds_array[0]=lisfd;


	while(1)
	{
		FD_ZERO(&rdset);
		for(i=0;i<ARRAY_NUM(fds_array);i++)
		{
			if(fds_array[i]<0)
			{continue;}
			FD_SET(fds_array[i],&rdset);
			maxfd=(fds_array[i] > maxfd ? fds_array[i] : maxfd);
		}

		/***********select start*************/
		printf("select waiting things...\n");
		rv=select(maxfd+1,&rdset,NULL,NULL,NULL);
		if(rv<0)
		{
			printf("select failure:%s\n",strerror(errno));
			continue;
		}
		else if(rv==0)
		{
			printf("select timeout\n");
			continue;
		}
		printf("\nselect accept new thing\n");
		/***********************************/


		/*** 接受新客户端模块********/
		if( FD_ISSET(lisfd,&rdset) )
		{
		
			printf("the thing is accept a new client\n");
			clifd=accept(lisfd,NULL,NULL);
			if(clifd<0)
			{
				printf("accept failure:%s\n",strerror(errno));
				continue;
			}
			printf("accept new client[%d],now add it to fds_array\n",clifd);

			/**** 将新客户端加入fds_array中****/
			for(i=0;i<ARRAY_NUM(fds_array);i++)
			{
			
				if(fds_array[i]<0)
				{
					fds_array[i]=clifd;
					break;
				}
			}
			/************************************/
		}
		/**************************************************************************/

		/******************** 接受到客户端数据 *********************************/
		else
		{
			printf("the thing is get data from client\n");
			memset(buf,0,sizeof(buf));

			/**** 遍历fds_array，查看哪个客户端发来数据*****/
			for(i=1;i<ARRAY_NUM(fds_array);i++)
			{
			 
				printf("now fds_array[%d]=%d\n",i,fds_array[i]);
				if(fds_array[i]<0 ||  !FD_ISSET(fds_array[i],&rdset))//不加该语句会有以下可能：某个中间到来的客户端，（即不是第一个连接的客户端，也不是最后一个连接的客户端），断开后，fds_array在中间某处被设为-1；当另一客户端发来数据，for循环可是遍历读fds_array，会读到值为-1的文件描述符。注意：不加"!FD_ISSET(fds_array[i],&rdset)"判断,系统就不知道是哪个客户端发来的数据，就会每个客户端都read一遍，若第一个客户端未发来数据，系统read不到数据，则程序会阻塞在read处，不会read下一个客户端；若加上该判断，系统read第一个客户端时，read不到数据，则系统会read下一个客户端
					continue;
				rv=read(fds_array[i],buf,sizeof(buf));
				if(rv<0)
				{
					printf("read data failure:%s\n",strerror(errno));
					break;
				}
				else if(rv==0)
				{
					printf(" client[%d] disconnect,now remove it from fds_array\n",fds_array[i]);
					close(fds_array[i]);
					fds_array[i]=-1;
					break;//为什么郭工不调用break？
				}
				else
				{
					printf("read data %d bytes from client[%d]:%s\n",rv,fds_array[i],buf);
	
					rv=write(fds_array[i],buf,strlen(buf));			
					if(rv<0)
					{
						printf("write failure:%s\n",strerror(errno));
						break;
					}
					printf("write data strlen %d back to client:%s\n",rv,buf);
					break;
				}
			}
			/***************************************************************/
		

		}
		/**************************************************************************/
	}

	for(i=0;i<ARRAY_NUM(fds_array);i++)
	{
		if(fds_array[i]<0)
			continue;
		close(fds_array[i]);
	}


	return 0;
}

inline void print_prog(char *progname)
{
	printf("Usage: %s [OPTION]...\n", progname);
        printf(" %s is a socket server program, which used to verify client and echo back string from it\n",
progname);
        printf("\nMandatory arguments to long options are mandatory for short options too:\n");
        printf(" -p[port ] Socket server port address\n");
        printf(" -h[help ] Display this help information\n");
        printf("\nExample: %s  -p 8900\n", progname);
        return ;

}
